from __future__ import with_statement

import sys
import os
import time
import shutil
import unittest
import traceback
import server
from kineta import *


class IndexTests(unittest.TestCase):

    def setUp(self):
        self._dbdir = "db"
        if os.path.isdir(self._dbdir):
            shutil.rmtree(self._dbdir)
        self.open()
        for i in xrange(30):
            self.db.insert("x", {"a": i, "b": i*2, "c": i*3})

    def _open(self):
        db = FileDB(self._dbdir)
        db.set_bg_error_cb(self.bgerror)
        db.limits.min_examined_create_index = 20
        db.limits.max_sort_create_index = 10
        db.limits.max_bookmark_lookup = 0
        return db

    def open(self):
        self.db = self._open()

    def bgerror(self, exc_info):
        traceback.print_exception(*exc_info)

    def waitForIndex(self, tbl):
        self.db.wait_for_bg_tasks()
        indexes = self.db.list_indexes(tbl)
        self.assertEqual(len(indexes), 1)
        return indexes[0]

    def testStartsWith(self):
        for i in xrange(30):
            self.db.insert("s", {"a": i, "b": str(i)})

        with self.db.select(table="s", fields=["b"],
                            where={"b": ['startswith', '2']}) as reader:
            res = list(reader)
        self.assertEqual(len(res), 11)
        res = [r[0] for r in res]
        for i in xrange(20, 30):
            self.assert_(str(i) in res)
        self.assert_('2' in res)
        self.assertEqual(self.waitForIndex('s'), [['b', 'a']])

        with self.db.select(table="s", fields=["b"],
                            where={"b": ['startswith', '1']}) as reader:
            res = list(reader)
        self.assertEqual(len(res), 11)
        res = [r[0] for r in res]
        for i in xrange(10, 20):
            self.assert_(str(i) in res)
        self.assert_('1' in res)

        plan = self.db.explain(table="s", fields=["b"],
                               where={"b": ['startswith', '1']})
        self.assertEqual(plan['strategy'], 'index_scan')

    def createMultiEqualIdx(self, tbl):
        with self.db.select(table = tbl,
                            fields = ["b"],
                            where = {"a": ("==", 10),
                                     "b": ("==", 20),
                                     "c": ("==", 30)}) as reader:
            res = list(reader)
        self.assertEqual(len(res), 1)
        self.assertEqual(len(res[0]), 1)
        self.assertEqual(res[0][0], 20)
        return self.waitForIndex(tbl)

    def testMultiEqual(self):
        idx = self.createMultiEqualIdx("x")
        self.assert_(idx is not None)
        self.assertEqual(len(idx), 3)
        self.assert_(["a", "a"] in idx)
        self.assert_(["b", "a"] in idx)
        self.assert_(["c", "a"] in idx)

        with self.db.select(table = "x",
                            fields = ["b"],
                            where = {"a": ("==", 10),
                                     "b": ("==", 20),
                                     "c": ("==", 30)}) as reader:
            res = list(reader)
        self.assertEqual(len(res), 1)
        self.assertEqual(len(res[0]), 1)
        self.assertEqual(res[0][0], 20)

    def testMultiEqualReopen(self):
        for i in xrange(30):
            self.db.insert("y", {"a": i, "b": i*2, "c": i*3})
        self.createMultiEqualIdx("x")
        self.createMultiEqualIdx("y")
        self.tearDown()
        self.open()
        indexes = self.db.list_indexes("x")
        self.assertEqual(len(indexes), 1)
        idx = indexes[0]
        self.assertEqual(len(idx), 3)
        self.assert_(["a", "a"] in idx)
        self.assert_(["b", "a"] in idx)
        self.assert_(["c", "a"] in idx)
        with self.db.select(table = "x",
                            fields = ["b"],
                            where = {"a": ("==", 10)}) as reader:
            res = list(reader)
        self.assertEqual(len(res), 1)
        self.assertEqual(len(res[0]), 1)
        self.assertEqual(res[0][0], 20)

    def betweenKernel(self):
        with self.db.select(table = "x",
                            fields = ["b"],
                            where = {"a": ("between", 20, 23)}) as reader:
            res = list(reader)
        self.assertEqual(len(res), 4)
        self.assertEqual(res[0][0], 40)
        self.assertEqual(res[1][0], 42)
        self.assertEqual(res[2][0], 44)
        self.assertEqual(res[3][0], 46)

    def exclBetweenKernel(self):
        with self.db.select(table = "x",
                            fields = ["b"],
                            where = {"a": ("exclbetween", 19, 24)}) as reader:
            res = list(reader)
        self.assertEqual(len(res), 4)
        self.assertEqual(res[0][0], 40)
        self.assertEqual(res[1][0], 42)
        self.assertEqual(res[2][0], 44)
        self.assertEqual(res[3][0], 46)

    def inKernel(self):
        with self.db.select(table = "x",
                            fields = ["b"],
                            where = {"a": ("in", 20, 21, 22, 23)}) as reader:
            res = list(reader)
        self.assertEqual(len(res), 4)
        self.assertEqual(res[0][0], 40)
        self.assertEqual(res[1][0], 42)
        self.assertEqual(res[2][0], 44)
        self.assertEqual(res[3][0], 46)

    def notInKernel(self):
        with self.db.select(table = "x",
                            fields = ["b"],
                            where = {"a": ("notin", ) +
                                    tuple(xrange(20)) +
                                    tuple(xrange(24, 40))}) \
                            as reader:
            res = list(reader)
        self.assertEqual(len(res), 4)
        self.assertEqual(res[0][0], 40)
        self.assertEqual(res[1][0], 42)
        self.assertEqual(res[2][0], 44)
        self.assertEqual(res[3][0], 46)

    def outsideKernel(self):
        with self.db.select(table = "x",
                            fields = ["b"],
                            where = {"a": ("outside", 1, 28)}) as reader:
            res = list(reader)
        self.assertEqual(len(res), 4)
        self.assertEqual(res[0][0], 0)
        self.assertEqual(res[1][0], 2)
        self.assertEqual(res[2][0], 56)
        self.assertEqual(res[3][0], 58)

    def exclOutsideKernel(self):
        with self.db.select(table = "x",
                            fields = ["b"],
                            where = {"a": ("excloutside", 2, 27)}) as reader:
            res = list(reader)
        self.assertEqual(len(res), 4)
        self.assertEqual(res[0][0], 0)
        self.assertEqual(res[1][0], 2)
        self.assertEqual(res[2][0], 56)
        self.assertEqual(res[3][0], 58)

    def neKernel(self):
        with self.db.select(table = "x",
                            fields = ["b"],
                            where = {"a": ("!=", 20)}) as reader:
            res = list(reader)
        self.assertEqual(len(res), 29)
        for row in res:
            self.assert_(row[0] != 40)

    def ltKernel(self):
        with self.db.select(table = "x",
                            fields = ["b"],
                            where = {"a": ("<", 4)}) as reader:
            res = list(reader)
        self.assertEqual(len(res), 4)
        self.assertEqual(res[0][0], 0)
        self.assertEqual(res[1][0], 2)
        self.assertEqual(res[2][0], 4)
        self.assertEqual(res[3][0], 6)

    def gtKernel(self):
        with self.db.select(table = "x",
                            fields = ["b"],
                            where = {"a": (">", 25)}) as reader:
            res = list(reader)
        self.assertEqual(len(res), 4)
        self.assertEqual(res[0][0], 52)
        self.assertEqual(res[1][0], 54)
        self.assertEqual(res[2][0], 56)
        self.assertEqual(res[3][0], 58)

    def ltEqKernel(self):
        with self.db.select(table = "x",
                            fields = ["b"],
                            where = {"a": ("<=", 3)}) as reader:
            res = list(reader)
        self.assertEqual(len(res), 4)
        self.assertEqual(res[0][0], 0)
        self.assertEqual(res[1][0], 2)
        self.assertEqual(res[2][0], 4)
        self.assertEqual(res[3][0], 6)

    def gtEqKernel(self):
        with self.db.select(table = "x",
                            fields = ["b"],
                            where = {"a": (">=", 26)}) as reader:
            res = list(reader)
        self.assertEqual(len(res), 4)
        self.assertEqual(res[0][0], 52)
        self.assertEqual(res[1][0], 54)
        self.assertEqual(res[2][0], 56)
        self.assertEqual(res[3][0], 58)

    def missingColKernel(self):
        with self.db.select(table = "x",
                            fields = ["a"],
                            where = {"missing": ("!=", None)}) as reader:
            res = list(reader)
        self.assertEqual(len(res), 0)

    def rangeTestSkel(self, kernel):
        kernel()

        idx = self.waitForIndex("x")

        self.assert_(idx is not None)
        self.assertEqual(len(idx), 1)
        self.assert_(["a", "a"] in idx)

        kernel()

    def testBetween(self):
        self.rangeTestSkel(self.betweenKernel)

    def testExclBetween(self):
        self.rangeTestSkel(self.exclBetweenKernel)

    def testIn(self):
        self.rangeTestSkel(self.inKernel)

    def testNotIn(self):
        self.rangeTestSkel(self.notInKernel)

    def testOutside(self):
        self.rangeTestSkel(self.outsideKernel)

    def testExclOutside(self):
        self.rangeTestSkel(self.exclOutsideKernel)

    def testNe(self):
        self.rangeTestSkel(self.neKernel)

    def testLt(self):
        self.rangeTestSkel(self.ltKernel)

    def testGt(self):
        self.rangeTestSkel(self.gtKernel)

    def testLtEq(self):
        self.rangeTestSkel(self.ltEqKernel)

    def testGtEq(self):
        self.rangeTestSkel(self.gtEqKernel)

    def testMissingCol(self):
        self.missingColKernel()

        idx = self.waitForIndex("x")

        self.assert_(idx is not None)
        self.assertEqual(len(idx), 1)
        self.assert_(["missing", "a"] in idx)

        self.missingColKernel()

    def orderTestSkel(self, ord, flds=("a", "b")):
        with self.db.select(table = "x",
                            fields = flds,
                            order = ord) as reader:
            res = list(reader)
        self.assertEqual(len(res), 30)
        first = res[0][0]
        for row in res:
            if ord[0][1] == "d":
                if row[0] > first:
                    self.fail()
            else:
                if row[0] < first:
                    self.fail()

        idx = self.waitForIndex("x")

        self.assert_(idx is not None)
        self.assertEqual(len(idx), len(flds))
        for o in ord:
            self.assert_(o in idx)

        with self.db.select(table = "x",
                            fields = flds,
                            where = {"a": ("<", 25)},
                            order = ord) as reader:
            res = list(reader)
        self.assertEqual(len(res), 25)
        first = res[0][0]
        for row in res:
            if ord[0][1] == "d":
                if row[0] > first:
                    self.fail()
            else:
                if row[0] < first:
                    self.fail()

    def testOrderD(self):
        self.orderTestSkel([["a", "d"]])

    def testOrderDA(self):
        self.orderTestSkel([["a", "d"], ["b", "a"]])

    def testOrderAD(self):
        self.orderTestSkel([["a", "a"], ["b", "d"]])

    def testOrderA(self):
        self.orderTestSkel([["a", "a"]])

    def testOrderAA(self):
        self.orderTestSkel([["a", "a"], ["b", "a"]])

    def testOrderDD(self):
        self.orderTestSkel([["a", "d"], ["b", "d"]])

    def testOrderOne(self):
        self.orderTestSkel([["a", "a"]], ["a"])

    def testOrderOneD(self):
        self.orderTestSkel([["a", "d"]], ["a"])

    def testBookmark(self):
        with self.db.select(table="x", fields=["a", "b"],
                             where={"a": ('==', 28),
                                    "b": ('>=', 0)}) as reader:
            res = list(reader)
        idx = self.waitForIndex("x")
        self.assert_(idx is not None)
        self.assertEqual(len(idx), 1)
        self.assert_(["a", "a"] in idx)
        with self.db.select(table="x", fields=["a", "b"],
                             where={"a": ('==', 28)}) as reader:
            res = list(reader)
        self.assertEqual(len(res), 1)
        self.db.wait_for_bg_tasks()
        indexes = self.db.list_indexes("x")
        self.assertEqual(len(indexes), 2)
        self.assert_([["a", "a"], ["b", "a"]] in indexes)

    def testLimitOffset(self):
        with self.db.select(table="x", fields=["a", "b", "c"],
                            order=["a"],
                            limit=3, offset=10) as reader:
            res = list(reader)
        self.assertEqual(len(res), 3)
        self.assertEqual(res[0], [10, 20, 30])
        self.assertEqual(res[1], [11, 22, 33])
        self.assertEqual(res[2], [12, 24, 36])

    def tearDown(self):
        self.db.close()


class IndexTestsRemote(IndexTests):

    def _open(self):
        return RemoteDB("http://localhost:20000/db")

    def open(self):
        self.server = server.TestServer()
        self.server.app.databases['db'].set_bg_error_cb(self.bgerror)
        self.server.app.databases['db'].limits.min_examined_create_index = 20
        self.server.app.databases['db'].limits.max_sort_create_index = 10
        self.server.app.databases['db'].limits.max_bookmark_lookup = 0
        self.db = self._open()

    def tearDown(self):
        self.server.quit()
        try:
            self.db.list_tables()
        except:
            pass
        self.db.close()
        self.server.join()
